package org.catspaw.cherubim.persistence.dao.jdbc;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import org.catspaw.cherubim.persistence.dao.ExtendedDao;
import org.catspaw.cherubim.persistence.jdbc.DefaultConnectionManager;
import org.catspaw.cherubim.persistence.jdbc.dbutils.SqlExecutor;
import org.catspaw.cherubim.persistence.mapping.DefaultEntityMappingResolver;
import org.catspaw.cherubim.persistence.mapping.EntityMappingResolver;
import org.catspaw.cherubim.persistence.mapping.EntityMappingUtils;
import org.catspaw.cherubim.util.GenericTypeUtils;

/**
 * 基于apache-commons-dbutils实现的Jdbc基类
 * @author 孙宁振
 * @param <E>实体类型
 * @param <P> 实体主键类型
 */
public abstract class BaseJdbcDao<E, P> implements ExtendedDao<E, P> {

	private SqlExecutor				executor;
	private EntityMappingResolver	entityMappingResolver;
	private Class<E>				entityClass;			//dao所持久化的实体class
	private Class<P>				idClass;

	public BaseJdbcDao() {
		this(new SqlExecutor(new DefaultConnectionManager()));
	}

	public BaseJdbcDao(SqlExecutor executor) {
		this.executor = executor;
	}

	private void initEntityMappingResolver() {
		if (entityMappingResolver == null) {
			entityMappingResolver = createEntityMappingResolver();
		}
	}

	protected EntityMappingResolver createEntityMappingResolver() {
		return new DefaultEntityMappingResolver(getEntityClass());
	}

	/**
	 * 取得entityClass的函数. JDK1.4不支持泛型的子类可以抛开Class<E> entityClass, 重新实现此函数达到相同效果。
	 */
	protected Class<E> getEntityClass() {
		if (entityClass == null) {
			entityClass = (Class<E>) GenericTypeUtils
					.getSuperClassGenericType(this.getClass());
		}
		return entityClass;
	}

	protected void setEntityClass(Class<E> entityClass) {
		this.entityClass = entityClass;
	}

	protected E[] createEmptyEntityArray(int length) {
		return (E[]) Array.newInstance(getEntityClass(), length);
	}

	protected Class<P> getIdClass() {
		if (idClass == null) {
			idClass = (Class<P>) GenericTypeUtils.getSuperClassGenericType(this
					.getClass(), 1);
		}
		return idClass;
	}

	protected void setIdClass(Class<P> idClass) {
		this.idClass = idClass;
	}

	protected String getIdFieldName() {
		return entityMappingResolver.getIdProperty();
	}

	protected String getTableName() {
		return entityMappingResolver.getTable();
	}

	public E create() {
		try {
			return getEntityClass().newInstance();
		} catch (InstantiationException e) {
			throw new IllegalStateException(e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * 总记录数
	 */
	public Number total() {
		initEntityMappingResolver();
		String sql = SqlUtils.generateTotalSql(entityMappingResolver);
		Number total = (Number) executor.query(sql, new ScalarHandler());
		return total;
	}

	public E get(P id) {
		initEntityMappingResolver();
		String sql = SqlUtils.generateGetSql(entityMappingResolver);
		E e = (E) executor.query(sql, new BeanHandler(getEntityClass()), id);
		return e;
	}

	public void save(E entity) {
		initEntityMappingResolver();
		String saveSql = SqlUtils.generateSaveSql(entityMappingResolver);
		String[] properties = entityMappingResolver.getProperties();
		Object[] params = EntityMappingUtils.getPropertyValues(entity,
																properties);
		executor.update(saveSql, params);
	}

	public P saveAndObtainId(E entity) {
		initEntityMappingResolver();
		String saveSql = SqlUtils.generateSaveSql(entityMappingResolver);
		String[] properties = entityMappingResolver.getProperties();
		Object[] params = EntityMappingUtils.getPropertyValues(entity,
																properties);
		P id = (P) EntityMappingUtils
				.getPropertyValue(entity, getIdFieldName());
		if (id == null) {
			id = (P) executor.insert(saveSql, params);
		} else {
			executor.update(saveSql, params);
		}
		return id;
	}

	public void saveAll(E[] entities) {
		if (entities.length < 1) {
			return;
		}
		initEntityMappingResolver();
		String saveAllSql = SqlUtils.generateSaveSql(entityMappingResolver);
		Object[][] params = new Object[entities.length][];
		for (int i = 0; i < entities.length; i++) {
			E entity = entities[i];
			String[] properties = entityMappingResolver.getProperties();
			params[i] = EntityMappingUtils
					.getPropertyValues(entity, properties);
		}
		executor.batch(saveAllSql, params);
	}

	public void saveAll(Collection<E> col) {
		E[] entities = col.toArray(createEmptyEntityArray(col.size()));
		saveAll(entities);
	}

	public void delete(E entity) {
		initEntityMappingResolver();
		String sql = SqlUtils.generateDeleteSql(entityMappingResolver);
		String idProperty = entityMappingResolver.getProperty(getIdFieldName());
		Object id = EntityMappingUtils.getPropertyValue(entity, idProperty);
		executor.update(sql, id);
	}

	public void deleteAll(Collection<E> entities) {
		initEntityMappingResolver();
		String sql = SqlUtils.generateDeleteSql(entityMappingResolver);
		for (E entity : entities) {
			String idProperty = entityMappingResolver
					.getProperty(getIdFieldName());
			Object id = EntityMappingUtils.getPropertyValue(entity, idProperty);
			executor.update(sql, id);
		}
	}

	public void deleteAll(E[] entities) {
		initEntityMappingResolver();
		String sql = SqlUtils.generateDeleteSql(entityMappingResolver);
		for (int i = 0; i < entities.length; i++) {
			String idProperty = entityMappingResolver
					.getProperty(getIdFieldName());
			Object id = EntityMappingUtils.getPropertyValue(entities[i],
															idProperty);
			executor.update(sql, id);
		}
	}

	public void deleteByProperty(String name, Object value) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("delete from ");
		builder.append(getTableName());
		builder.append(" where ");
		builder.append(entityMappingResolver.getColumn(name));
		if (value != null) {
			builder.append(" =?");
			executor.update(builder.toString(), value);
		} else {
			builder.append(" is null");
			executor.update(builder.toString());
		}
	}

	public void deleteInProperties(String name, Object[] value) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("delete from ");
		builder.append(getTableName());
		builder.append(" where ");
		builder.append(entityMappingResolver.getColumn(name));
		if (value == null || value.length < 1) {
			builder.append(" is null ");
		} else if (value.length < 2) {
			builder.append("=?");
		} else {
			builder.append(" in(");
			for (int i = 0; i < value.length; i++) {
				builder.append("?");
				if (i < value.length - 1) {
					builder.append(",");
				}
			}
			builder.append(") ");
		}
		executor.update(builder.toString(), value);
	}

	public void deleteByProperties(String[] names, Object[] values) {
		if (names.length != values.length) {
			throw new IllegalArgumentException(
					"the length of names and values are not equal!");
		}
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("delete from ");
		builder.append(getTableName());
		builder.append(" where ");
		for (int i = 0; i < names.length; i++) {
			builder.append(entityMappingResolver.getColumn(names[i]));
			if (values[i] != null) {
				builder.append(" =?");
			} else {
				builder.append(" is null");
			}
			if (i < names.length - 1) {
				builder.append(" and ");
			}
		}
		String sql = builder.toString();
		List<Object> valueList = new ArrayList<Object>();
		for (int i = 0; i < values.length; i++) {
			if (values[i] != null) {
				valueList.add(values[i]);
			}
		}
		values = valueList.toArray();
		executor.update(sql, values);
	}

	public void update(E entity) {
		initEntityMappingResolver();
		String updateSql = SqlUtils.generateUpdateSql(entityMappingResolver);
		String[] properties = entityMappingResolver.getProperties();
		Object[] params = EntityMappingUtils.getPropertyValues(entity,
																properties);
		executor.update(updateSql, params);
	}

	public void updateAll(E[] entities) {
		initEntityMappingResolver();
		String updateSql = SqlUtils.generateUpdateSql(entityMappingResolver);
		Object[][] params = new Object[entities.length][];
		String[] properties = entityMappingResolver.getProperties();
		for (int i = 0; i < entities.length; i++) {
			params[i] = EntityMappingUtils.getPropertyValues(entities[i],
																properties);
		}
		executor.batch(updateSql, params);
	}

	public void updateAll(Collection<E> entities) {
		initEntityMappingResolver();
		String updateSql = SqlUtils.generateUpdateSql(entityMappingResolver);
		Object[][] params = new Object[entities.size()][];
		String[] properties = entityMappingResolver.getProperties();
		int i = 0;
		for (E entity : entities) {
			params[i] = EntityMappingUtils
					.getPropertyValues(entity, properties);
			i++;
		}
		executor.batch(updateSql, params);
	}

	public List<E> findAll() {
		initEntityMappingResolver();
		String sql = SqlUtils.generateFindAllSql(entityMappingResolver);
		return (List<E>) executor.query(sql, new BeanListHandler(
				getEntityClass()));
	}

	public List<E> findByProperty(String name, Object value) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("select * from ");
		builder.append(getTableName());
		builder.append(" where ");
		builder.append(entityMappingResolver.getColumn(name));
		if (value != null) {
			builder.append(" =?");
		} else {
			builder.append(" is null");
		}
		String sql = builder.toString();
		if (value != null) {
			return (List<E>) executor.query(sql, new BeanListHandler(
					getEntityClass()), value);
		} else {
			return (List<E>) executor.query(sql, new BeanListHandler(
					getEntityClass()));
		}
	}

	public List<E> findInProperties(String name, Object[] value) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("select * from ");
		builder.append(getTableName());
		builder.append(" where ");
		builder.append(entityMappingResolver.getColumn(name));
		if (value == null || value.length < 1) {
			builder.append(" is null ");
		} else if (value.length < 2) {
			builder.append("=?");
		} else {
			builder.append(" in(");
			for (int i = 0; i < value.length; i++) {
				builder.append("?");
				if (i < value.length - 1) {
					builder.append(",");
				}
			}
			builder.append(") ");
		}
		String sql = builder.toString();
		return (List<E>) executor.query(sql, new BeanListHandler(
				getEntityClass()), value);
	}

	public List<E> findByProperties(String[] names, Object[] values) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("select * from ");
		builder.append(getTableName());
		builder.append(" where ");
		for (int i = 0; i < names.length; i++) {
			builder.append(entityMappingResolver.getColumn(names[i]));
			if (values[i] != null) {
				builder.append(" =?");
			} else {
				builder.append(" is null");
			}
			if (i < names.length - 1) {
				builder.append(" and ");
			}
		}
		String sql = builder.toString();
		List<Object> valueList = new ArrayList<Object>();
		for (int i = 0; i < values.length; i++) {
			if (values[i] != null) {
				valueList.add(values[i]);
			}
		}
		values = valueList.toArray();
		return (List<E>) executor.query(sql, new BeanListHandler(
				getEntityClass()), values);
	}

	public E findByUnique(String name, Object value) {
		List<E> list = findByProperty(name, value);
		if (!list.isEmpty()) {
			return list.get(0);
		}
		return null;
	}

	public Number countByProperty(String name, Object value) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("select count(1) from ");
		builder.append(getTableName());
		builder.append(" where ");
		builder.append(entityMappingResolver.getColumn(name));
		if (value != null) {
			builder.append(" =?");
		} else {
			builder.append(" is null");
		}
		String sql = builder.toString();
		if (value != null) {
			return (Number) executor.query(sql, new ScalarHandler(), value);
		} else {
			return (Number) executor.query(sql, new ScalarHandler());
		}
	}

	public Number countInProperties(String name, Object[] value) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("select count(1) from ");
		builder.append(getTableName());
		builder.append(" where ");
		builder.append(entityMappingResolver.getColumn(name));
		if (value == null || value.length < 1) {
			builder.append(" is null ");
		} else if (value.length < 2) {
			builder.append("=?");
		} else {
			builder.append(" in(");
			for (int i = 0; i < value.length; i++) {
				builder.append("?");
				if (i < value.length - 1) {
					builder.append(",");
				}
			}
			builder.append(") ");
		}
		String sql = builder.toString();
		return (Number) executor.query(sql, new ScalarHandler(), value);
	}

	public Number countByProperties(String[] names, Object[] values) {
		initEntityMappingResolver();
		StringBuilder builder = new StringBuilder("select count(*) from ");
		builder.append(getTableName());
		builder.append(" where ");
		for (int i = 0; i < names.length; i++) {
			builder.append(entityMappingResolver.getColumn(names[i]));
			if (values[i] != null) {
				builder.append(" =?");
			} else {
				builder.append(" is null");
			}
			if (i < names.length - 1) {
				builder.append(" and ");
			}
		}
		String sql = builder.toString();
		List<Object> valueList = new ArrayList<Object>();
		for (int i = 0; i < values.length; i++) {
			if (values[i] != null) {
				valueList.add(values[i]);
			}
		}
		values = valueList.toArray();
		return (Number) executor.query(sql, new BeanListHandler(
				getEntityClass()), values);
	}

	public List<E> findByExample(E entity) {
		throw new UnsupportedOperationException("unsupported operation...");
	}

	protected EntityMappingResolver getEntityMappingResolver() {
		return entityMappingResolver;
	}

	protected SqlExecutor getExecutor() {
		return executor;
	}
}
